package fatworm.query;

import java.util.HashSet;
import java.util.LinkedList;

import fatworm.query.constant.Constant;
import fatworm.query.constant.FloatConstant;
import fatworm.query.constant.IntConstant;

public class GroupByScan implements Scan {

	private Scan s;
	private String name, newname, fieldname;
	private LinkedList<Constant> vallist;
	private Constant comp, cur;
	private HashSet<Integer> map;
	private int index;
	private Predicate pred;
	
	public GroupByScan(Scan s, String fieldname, String cmd, String name, Predicate pred) {
		this.s = s;
		this.fieldname = fieldname;
		this.name = name;
		this.pred = pred;
		vallist = new LinkedList<Constant>();
		map = new HashSet<Integer>();
		runGroup(cmd);
	}
	
	private boolean init() {
		index = 0;
		map.add(index);
		s.beforeFirst();
		
		boolean flag;
		
		while (flag = s.next() && (map.contains(index) || !pred.isOK(s)))
			index++;
		
		if (!flag) 
			return false;
		
		map.add(index);
		
		comp = s.getVal(fieldname);
		
		return true;
	}
	
	private boolean getNext() {
		
		while (s.next()) 
			if (!map.contains(index) && pred.isOK(s)) {
				cur = s.getVal(fieldname);
				
				if (comp.equals(cur))
					return true;
			}
		
		return false;
	}
	
	private void runAvg() {
		newname = "AVG(" + name + ")";
		
		while (true) {
			Constant sum = null;
			int count = 0;
			
			if (!init()) 
				break;
			
			while (getNext()) {
				count++;
				if (sum == null)
					sum = s.getVal(name);
				else
					sum = sum.plus(s.getVal(name));
			}
			
			float avg;
			if (sum instanceof IntConstant)
				avg = ((Integer)sum.asJavaVal()).floatValue() / count;
			else
				avg = ((Float)sum.asJavaVal()).floatValue() / count;
			
			for (Integer i : map)
				if (vallist.get(i) == null)
					vallist.set(i, new FloatConstant(avg));
		}
	}
	
	private void runCount() {
		newname = "COUNT(" + name + ")";
		
		while (true) {
			int count = 0;
			
			if (!init()) 
				break;
			
			while (getNext())
				count++;
			
			for (Integer i : map)
				if (vallist.get(i) == null)
					vallist.set(i, new IntConstant(count));
		}
		
	}
	
	private void runMin() {
		newname = "MIN(" + name + ")";
		
		while (true) {
			Constant min = null;

			if (!init()) 
				break;
			
			while (getNext()) 
				if (min == null || min.compareTo(s.getVal(name)) > 0)
					min = s.getVal(name);
			
			for (Integer i : map)
				if (vallist.get(i) == null)
					vallist.set(i, min);
		}
	}
	
	private void runMax() {
		newname = "MAX(" + name + ")";
		
		while (true) {
			Constant max = null;

			if (!init()) 
				break;
			
			while (getNext()) 
				if (max == null || max.compareTo(s.getVal(name)) < 0)
					max = s.getVal(name);
			
			for (Integer i : map)
				if (vallist.get(i) == null)
					vallist.set(i, max);
		}
	}
	
	private void runSum() {
		newname = "SUM(" + name + ")";
		
		while (true) {
			Constant sum = null;
			
			if (!init()) 
				break;
			
			while (getNext()) 
				if (sum == null)
					sum = s.getVal(name);
				else
					sum = sum.plus(s.getVal(name));
			
			for (Integer i : map)
				if (vallist.get(i) == null)
					vallist.set(i, sum);
		}
	}
	
	private void runGroup(String cmd) {
		s.beforeFirst();
		while (s.next()) 
			vallist.add(null);
		
		if (cmd.equals("AVG"))
			runAvg();
		else if (cmd.equals("COUNT"))
			runCount();
		else if (cmd.equals("MIN")) 
			runMin();
		else if (cmd.equals("MAX"))
			runMax();
		else if (cmd.equals("SUM"))
			runSum();
	}
	
	public void beforeFirst() {
		s.beforeFirst();
		index = -1;
	}
	
	public boolean next() {
		index++;
		return s.next();
	}
	
	public void close() {
		s.close();
	}
	
	public Constant getVal(String name) {
		if (newname.equals(name))
			return vallist.get(index);
		else
			return s.getVal(name);
	}
	
/*	public int getInt(String name) {
		if (newname.equals(name))
			return ((Integer)vallist.get(index).asJavaVal()).intValue();
		else
			return s.getInt(name);
	}
	
	public String getString(String name) {
		if (newname.equals(name))
			return (String)vallist.get(index).asJavaVal();
		else
			return s.getString(name);
	}*/
	
	public boolean hasField(String name) {
		if (newname.equals(name))
			return true;
		else
			return s.hasField(name);
	}
}